#include <pr/Pattern.h>
#include <util/Formatter.h>
#include <gtest/gtest.h>
#include <cmath>
#include <stdexcept>

using namespace pr;

TEST(TestPattern, testCtor00)
{
  Pattern p;

  ASSERT_EQ(0, p.featureCount());
}

TEST(TestPattern, testCtor01)
{
  Pattern p(12);

  util::Formatter::getInstance().toStream(p, std::cout);

  ASSERT_EQ(12, p.featureCount());
  for (std::size_t index = 0; index < p.featureCount(); ++index)
    ASSERT_DOUBLE_EQ(0, p[index]);

  ASSERT_DOUBLE_EQ(0, p.min());
  ASSERT_DOUBLE_EQ(0, p.max());
}

TEST(TestPattern, testCtor02)
{
  std::vector<int> v(5, 3);
  Pattern p(v);

  ASSERT_EQ(5, p.featureCount());
  for (std::size_t index = 0; index < p.featureCount(); ++index)
    ASSERT_DOUBLE_EQ(3, p[index]);

  ASSERT_DOUBLE_EQ(3, p.min());
  ASSERT_DOUBLE_EQ(3, p.max());
}

TEST(TestPattern, testCtor03)
{
  std::vector<util::Real> v(5, 3.5);
  Pattern p(v);

  ASSERT_EQ(5, p.featureCount());
  for (std::size_t index = 0; index < p.featureCount(); ++index)
    ASSERT_DOUBLE_EQ(3.5, p[index]);

  ASSERT_DOUBLE_EQ(3.5, p.min());
  ASSERT_DOUBLE_EQ(3.5, p.max());
}

TEST(TestPattern, testGetSetFeature)
{
  std::vector<util::Real> v(5, 3.5);
  Pattern p(v);

  // set/getFeature
  for (std::size_t index = 0; index < p.featureCount(); ++index)
  {
    util::Real currentValue = p.getFeature(index);
    p.setFeature(index, currentValue + index);
    ASSERT_DOUBLE_EQ(currentValue + index, p.getFeature(index));
  }

  // operator[]
  for (std::size_t index = 0; index < p.featureCount(); ++index)
  {
    util::Real currentValue = p[index];
    p[index] = currentValue + index;
    ASSERT_DOUBLE_EQ(currentValue + index, p[index]);
  }
}

TEST(TestPattern, testNormInfinite)
{
  std::vector<util::Real> v(5, 3.5);
  Pattern p(v);
  p[3] = 1;
  p[4] = -0.1;

  ASSERT_DOUBLE_EQ(3.5, p.normInfinite());

  p.normalizeInfinite();
  ASSERT_DOUBLE_EQ(1.0, p.normInfinite());
  ASSERT_DOUBLE_EQ(1, p[0]);
  ASSERT_DOUBLE_EQ(1, p[1]);
  ASSERT_DOUBLE_EQ(1, p[2]);
  ASSERT_DOUBLE_EQ(1.0/3.5, p[3]);
  ASSERT_DOUBLE_EQ(-0.1/3.5, p[4]);
}

TEST(TestPattern, testNorm1)
{
  std::vector<util::Real> v(5, 3.5);
  Pattern p(v);
  p[3] = 1;
  p[4] = -0.1;

  util::Real norm1 = 3.5 + 3.5 + 3.5 + 1 + 0.1;
  ASSERT_DOUBLE_EQ(norm1, p.norm1());
}

TEST(TestPattern, testNorm2)
{
  std::vector<util::Real> v(5, 3.5);
  Pattern p(v);
  p[3] = 1;
  p[4] = -0.1;

  util::Real norm2 = sqrt(3.5*3.5 + 3.5*3.5 + 3.5*3.5 + 1*1 + (-0.1)*(-0.1));
  ASSERT_DOUBLE_EQ(norm2, p.norm2());

  p.normalize2();
  ASSERT_DOUBLE_EQ(1, p.norm2());
  ASSERT_DOUBLE_EQ(3.5/norm2, p[0]);
  ASSERT_DOUBLE_EQ(3.5/norm2, p[1]);
  ASSERT_DOUBLE_EQ(3.5/norm2, p[2]);
  ASSERT_DOUBLE_EQ(1.0/norm2, p[3]);
  ASSERT_DOUBLE_EQ(-0.1/norm2, p[4]);

  // Normalizing an already normalized pattern doesn't change anything
  p.normalize2();
  ASSERT_DOUBLE_EQ(1, p.norm2());
  ASSERT_DOUBLE_EQ(3.5/norm2, p[0]);
  ASSERT_DOUBLE_EQ(3.5/norm2, p[1]);
  ASSERT_DOUBLE_EQ(3.5/norm2, p[2]);
  ASSERT_DOUBLE_EQ(1.0/norm2, p[3]);
  ASSERT_DOUBLE_EQ(-0.1/norm2, p[4]);
}

TEST(TestPattern, testAdd1)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;
  std::vector<util::Real> v2(5, 1.5);
  Pattern p2(v2);
  p2[3] = -0.5;

  p1 += p2;

  util::Real sum = 3.5 + 1.5;
  ASSERT_DOUBLE_EQ(sum, p1[0]);
  ASSERT_DOUBLE_EQ(sum, p1[4]);

  ASSERT_DOUBLE_EQ(-1 + 1.5, p1[2]);
  ASSERT_DOUBLE_EQ(3.5 - 0.5, p1[3]);
}

TEST(TestPattern, testAdd2)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;
  std::vector<util::Real> v2(5, 1.5);
  Pattern p2(v2);
  p2[3] = -0.5;

  Pattern pSum1 = p1 + p2;
  Pattern pSum2 = p2 + p1;

  util::Real sum = 3.5 + 1.5;
  ASSERT_DOUBLE_EQ(sum, pSum1[0]);
  ASSERT_DOUBLE_EQ(sum, pSum1[4]);
  ASSERT_DOUBLE_EQ(sum, pSum2[0]);
  ASSERT_DOUBLE_EQ(sum, pSum2[4]);

  ASSERT_DOUBLE_EQ(-1 + 1.5, pSum1[2]);
  ASSERT_DOUBLE_EQ(3.5 - 0.5, pSum2[3]);
}

TEST(TestPattern, testSubtract1)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;
  std::vector<util::Real> v2(5, 1.5);
  Pattern p2(v2);
  p2[3] = -0.5;

  p1 -= p2;

  util::Real diff = 3.5 - 1.5;
  ASSERT_DOUBLE_EQ(diff, p1[0]);
  ASSERT_DOUBLE_EQ(diff, p1[4]);

  ASSERT_DOUBLE_EQ(-1 - 1.5, p1[2]);
  ASSERT_DOUBLE_EQ(3.5 - (-0.5), p1[3]);
}

TEST(TestPattern, testSubtract2)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;
  std::vector<util::Real> v2(5, 1.5);
  Pattern p2(v2);
  p2[3] = -0.5;

  Pattern pDiff = p1 - p2;

  util::Real diff = 3.5 - 1.5;
  ASSERT_DOUBLE_EQ(diff, pDiff[0]);
  ASSERT_DOUBLE_EQ(diff, pDiff[4]);

  ASSERT_DOUBLE_EQ(-1 - 1.5, pDiff[2]);
  ASSERT_DOUBLE_EQ(3.5 - (-0.5), pDiff[3]);
}

TEST(TestPattern, testAddScalar1)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  p1 += 1.5;

  util::Real sum = 3.5 + 1.5;
  ASSERT_DOUBLE_EQ(sum, p1[0]);
  ASSERT_DOUBLE_EQ(sum, p1[4]);

  ASSERT_DOUBLE_EQ(-1 + 1.5, p1[2]);
}

TEST(TestPattern, testAddScalar2)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  Pattern pSum = p1 + 1.5;

  util::Real sum = 3.5 + 1.5;
  ASSERT_DOUBLE_EQ(sum, pSum[0]);
  ASSERT_DOUBLE_EQ(sum, pSum[4]);

  ASSERT_DOUBLE_EQ(-1 + 1.5, pSum[2]);
}

TEST(TestPattern, testSubtractScalar1)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  p1 -= 1.5;

  util::Real diff = 3.5 - 1.5;
  ASSERT_DOUBLE_EQ(diff, p1[0]);
  ASSERT_DOUBLE_EQ(diff, p1[4]);

  ASSERT_DOUBLE_EQ(-1 - 1.5, p1[2]);
}

TEST(TestPattern, testSubtractScalar2)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  Pattern pDiff = p1 - 1.5;

  util::Real diff = 3.5 - 1.5;
  ASSERT_DOUBLE_EQ(diff, pDiff[0]);
  ASSERT_DOUBLE_EQ(diff, pDiff[4]);

  ASSERT_DOUBLE_EQ(-1 - 1.5, pDiff[2]);
}

TEST(TestPattern, testMultiplyScalar1)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  p1 *= 1.5;

  util::Real product = 3.5 * 1.5;
  ASSERT_DOUBLE_EQ(product, p1[0]);
  ASSERT_DOUBLE_EQ(product, p1[4]);

  ASSERT_DOUBLE_EQ(-1 * 1.5, p1[2]);
}

TEST(TestPattern, testMultiplyScalar2)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  Pattern pProduct = p1 * 1.5;

  util::Real product = 3.5 * 1.5;
  ASSERT_DOUBLE_EQ(product, pProduct[0]);
  ASSERT_DOUBLE_EQ(product, pProduct[4]);

  ASSERT_DOUBLE_EQ(-1 * 1.5, pProduct[2]);
}

TEST(TestPattern, testDivideScalar1)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  p1 /= 1.5;

  util::Real div = 3.5 / 1.5;
  ASSERT_DOUBLE_EQ(div, p1[0]);
  ASSERT_DOUBLE_EQ(div, p1[4]);

  ASSERT_DOUBLE_EQ(-1 / 1.5, p1[2]);
}

TEST(TestPattern, testDivideScalar2)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  Pattern pDiv = p1 / 1.5;

  util::Real div = 3.5 / 1.5;
  ASSERT_DOUBLE_EQ(div, pDiv[0]);
  ASSERT_DOUBLE_EQ(div, pDiv[4]);

  ASSERT_DOUBLE_EQ(-1 / 1.5, pDiv[2]);
}

TEST(TestPattern, testDivideBy0)
{
  std::vector<util::Real> v1(5, 3.5);
  Pattern p1(v1);
  p1[2] = -1;

  ASSERT_THROW(Pattern pDiv = p1 / 0, std::invalid_argument);
  ASSERT_THROW(p1 /= 0, std::invalid_argument);
}
